﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace PageAdmin.Utils
{

    /// <summary>
    /// 与http请求有关的help帮助类,实例方法
    /// </summary>
    public class HttpRequestHelper
    {
        /// <summary>
        /// 来路,解决防盗链问题
        /// </summary>
        public string Referer { set; get; }

        /// <summary>
        /// Request的ContentType
        /// </summary>
        public string RequestContentType { set;get; } = "application/x-www-form-urlencoded";
        /// <summary>
        /// 默认编码,默认utf-8
        /// </summary>
        public string EncodingName { set; get; } = "UTF-8";

        /// <summary>
        /// 单位秒
        /// </summary>
        public int TimeoutSecond { set; get; } = 5000;

        /// <summary>
        /// 请求头参数字典
        /// </summary>
        public Dictionary<string, string> HeaderDictionary { set; get; }

        /// <summary>
        /// 传递的Cookie
        /// </summary>
        public Dictionary<string, string> CookieDictionary  { set; get; }

        /// <summary>
        /// 是否支持重定向
        /// </summary>
        public bool AllowAutoRedirect { set; get; } = false;

        /// <summary>
        /// 是否模拟手机浏览器代理信息
        /// </summary>
        public bool IsMobileAgent { get; set; } = false;

        /// <summary>
        /// Response的ContentType
        /// </summary>
        public string ResponseContentType { get; private set; }

        /// <summary>
        /// 判断请求是否是ajax
        /// </summary>
        /// <returns></returns>
        public bool IsAjaxRequest()
        {
            string header = HttpContext.Current.Request.Headers["X-Requested-With"];
            return (header != null && header == "XMLHttpRequest") ? true : false;
        }


        /// <summary>
        /// 保存远程http文件到服务器本地
        /// </summary>
        /// <param name="httpurl">远程文件路径</param>
        /// <param name="saveLocalPath">本地保存路径,必须是完整的文件名路径，不能是目录</param>
        /// <returns></returns>
        public string Save(string httpPath, string saveLocalPath)
        {
            saveLocalPath = IOHelper.MapPath(saveLocalPath);
            string fileName = Path.GetFileName(saveLocalPath);
            if (fileName.IndexOf(".") < 0)
            {
                throw new Exception("对不起，保存路径必须包含完整的文件路径！");
            }
            HttpWebRequest httpRequest = null;
            Stream stream = null;
            HttpWebResponse httpWebResponse = null;
            try
            {
                stream = GetStream(httpPath, out httpRequest, out httpWebResponse);
                IOHelper.SaveFile(stream, saveLocalPath);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                Close(httpRequest, httpWebResponse, stream);
            }
            return saveLocalPath;
        }

        /// <summary>
        /// 获取远程http内容
        /// </summary>
        /// <param name="httpurl"></param>
        /// <returns></returns>
        public string Get(string httpurl) //读取远程url返回内容
        {
            Encoding encoding = Encoding.GetEncoding(EncodingName);
            HttpWebRequest httpRequest = null;
            StreamReader streamReader = null;
            Stream stream = null;
            HttpWebResponse httpWebResponse = null;
            string content = null;
            try
            {
                stream = GetStream(httpurl, out httpRequest, out httpWebResponse);
                streamReader = new StreamReader(stream, encoding);
                content = streamReader.ReadToEnd();
            }
            catch (WebException ex)
            {
                string statusDescription = "";
                if (ex.Response != null)
                {
                    statusDescription = "," + ((HttpWebResponse)ex.Response).StatusDescription;
                }
                throw new Exception(ex.Message + statusDescription);
            }
            finally
            {
                if (streamReader != null)
                {
                    streamReader.Close();
                }
                Close(httpRequest, httpWebResponse, stream);
            }
            return content;
        }

        public async Task<string> GetAsync(string httpurl)
        {
            string strGetResponse = string.Empty;
            try
            {
                var getRequest = CreateGetHttpWebRequest(httpurl);
                var getResponse = await getRequest.GetResponseAsync() as HttpWebResponse;
                if (IsMobileAgent)
                {
                    getRequest.UserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1";
                }
                else
                {
                    getRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0";
                }
                using (StreamReader reader = new StreamReader(getResponse.GetResponseStream(), Encoding.GetEncoding(EncodingName)))
                {
                    strGetResponse = reader.ReadToEnd();
                }
            }
            catch (Exception ex)
            {
                strGetResponse = ex.Message;
            }
            return strGetResponse;
        }

        private HttpWebRequest CreateGetHttpWebRequest(string url)
        {
            var getRequest = HttpWebRequest.Create(url) as HttpWebRequest;
            getRequest.Method = "GET";
            getRequest.Timeout = TimeoutSecond * 1000;
            getRequest.ContentType = $"{RequestContentType};charset={EncodingName}";
            //getRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
            return getRequest;
        }
        private string GetHttpResponse(HttpWebResponse response)
        {
            var responseResult = "";

            return responseResult;
        }


        /// <summary>
        /// 获取远程http内容
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="getParams">请求参数</param>
        /// <returns></returns>
        public string Get(string httpurl, Dictionary<string, string> getParams) //读取远程url返回内容
        {
            if (getParams != null)
            {
                string queryString = GetQueryString(getParams);
                if (!httpurl.Contains("?"))
                {
                    httpurl = httpurl + "?";
                }
                httpurl = httpurl + "&" + queryString;
            }
            return Get(httpurl);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="postParams"></param>
        /// <returns></returns>
        public string Post(string httpurl)
        {
            return Post(httpurl,"");
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="postParams"></param>
        /// <returns></returns>
        public string Post(string httpurl, Dictionary<string, string> postParams)
        {
            string postData = GetQueryString(postParams);
            return Post(httpurl, postData);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="formData"></param>
        /// <returns></returns>
        public string Post(string httpurl, NameValueCollection formData)
        {
            var dic = IEnumerableHelper.FormatDictionary(formData);
            return Post(httpurl, dic);
        }

        /// <summary>
        /// post请求
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        private string Post(string httpurl, string data)
        {
            if (!httpurl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !httpurl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                throw new Exception("对不起，请求的地址必须以http://或https开头");
            }
            Encoding encoding = Encoding.GetEncoding(EncodingName);
            string postData = data;
            byte[] bTemp = encoding.GetBytes(postData);
            HttpWebRequest httpRequest = null;
            Stream stream = null;
            HttpWebResponse hwResponse = null;
            StreamReader streamReader = null;
            string result = null;
            try
            {
                //https协议必须添加，否则请求会被中断或阻止
                if (httpurl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                {
                    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
                }
                httpRequest = (HttpWebRequest)WebRequest.Create(httpurl);
                if (!string.IsNullOrEmpty(Referer))
                {
                    httpRequest.Referer = Referer;
                }
                httpRequest.Proxy = null;
                httpRequest.CachePolicy = new RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                if (HeaderDictionary != null)
                {
                    foreach (KeyValuePair<string, string> keyValue in HeaderDictionary)
                    {
                        httpRequest.Headers[keyValue.Key] = keyValue.Value;
                    }
                }
                //添加cookie
                SetCookie(httpRequest, httpurl);
                httpRequest.AllowAutoRedirect = AllowAutoRedirect;
                httpRequest.Timeout = TimeoutSecond * 1000;
                httpRequest.Method = "Post";
                httpRequest.ContentType = $"{RequestContentType};charset={EncodingName}";
                if (IsMobileAgent)
                {
                    httpRequest.UserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Mobile Safari/537.36";
                }
                else
                {
                    httpRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36";
                }
                httpRequest.ContentLength = bTemp.Length;
                stream = httpRequest.GetRequestStream();
                stream.Write(bTemp, 0, bTemp.Length);
                hwResponse = (HttpWebResponse)httpRequest.GetResponse();//发出请求
                streamReader = new StreamReader(hwResponse.GetResponseStream(), encoding);//接收
                ResponseContentType = hwResponse.ContentType;
                result = streamReader.ReadToEnd();
            }
            catch (WebException ex)
            {
                string statusDescription = "";
                if (ex.Response != null)
                {
                    statusDescription = "," + ((HttpWebResponse)ex.Response).StatusDescription;
                }
                throw new Exception(ex.Message + statusDescription);
            }
            finally
            {
                if (streamReader != null)
                {
                    streamReader.Close();
                }
                Close(httpRequest, hwResponse, stream);
            }
            return result;
        }

        private static bool CheckValidationResult(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors errors)
        {
            return true; //总是接受  
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="filePath">要下载的文件，文件必须能通过浏览器直接下载</param>
        /// <param name="saveName">下载时保存文件名</param>
        /// <param name="perReadByte">每次读取的字节数</param>
        /// <param name="timeoutSecond">超时时间</param>

        public TipsInfo Download(string httpUrl, string saveName = null, int perReadByte = 8192)
        {
            TipsInfo tipsInfo = new TipsInfo();
            if (string.IsNullOrEmpty(httpUrl))
            {
                tipsInfo.State = 0;
                tipsInfo.Msg = "HttpUrl is IsNullOrEmpty";
                return tipsInfo;
            }
            HttpResponse response = HttpContext.Current.Response;
            if (string.IsNullOrEmpty(saveName))
            {
                saveName = Path.GetFileName(httpUrl);
            }
            else
            {
                saveName = Path.GetFileNameWithoutExtension(saveName) + Path.GetExtension(httpUrl);
            }
            //不是http开头的加上http前缀
            if (!httpUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !httpUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                string httpPrefix = StringHelper.DomainFormat(HttpContext.Current.Request.Url.ToString(), true);
                httpUrl = StringHelper.Combine(httpPrefix, httpUrl);
            }

            //禁止下载http://www.pageadmin.net这类没有指定文件名的地址
            string validateUrl = httpUrl.ToLower().Replace("http://", "").Replace("https://", "");
            string[] arrayValidateUrl = validateUrl.Split('/');
            if (arrayValidateUrl.Length < 2)
            {
                tipsInfo.State = 0;
                tipsInfo.Msg = "Invalid file";
                return tipsInfo;
            }
            string validateName = Path.GetFileName(validateUrl);
            if (string.IsNullOrEmpty(validateName))
            {
                tipsInfo.State = 0;
                tipsInfo.Msg = "Invalid filename";
                return tipsInfo;
            }

            HttpWebRequest httpWebRequest = null;
            Stream stream = null;
            HttpWebResponse httpWebResponse = null;
            byte[] fileBuffer = new byte[perReadByte];
            try
            {
                using (stream = GetStream(httpUrl, out httpWebRequest, out httpWebResponse))
                {
                    response.Clear();
                    response.ClearHeaders();
                    //ResponseStream不支持长度查询，只能注释掉，需要转换为内存流，大文件有点浪费内存。
                    //response.AddHeader("Content-Length", stream.Length.ToString());
                    response.ContentType = "application/octet-stream";
                    if (!string.IsNullOrEmpty(Referer))
                    {
                        httpWebRequest.Referer = Referer;
                    }
                    response.AddHeader("Content-Disposition", "attachment;filename=" + saveName);// HttpUtility.UrlEncode(fileName,Encoding.UTF8));
                    int readLength = stream.Read(fileBuffer, 0, fileBuffer.Length);
                    while (readLength > 0 && response.IsClientConnected)//持续传输文件
                    {
                        response.OutputStream.Write(fileBuffer, 0, readLength);//写入到响应的输出流
                        response.Flush();//把缓存输出
                        readLength = stream.Read(fileBuffer, 0, fileBuffer.Length);
                    }
                    //response.Close();//如果没有设置Content-Length，则必须注释掉，否则iis上下载不了文件
                    return tipsInfo;
                }
            }
            catch (Exception ex)
            {
                tipsInfo.State = 0;
                tipsInfo.Msg = ex.Message;
            }
            finally
            {
                Close(httpWebRequest, httpWebResponse, stream);
            }
            return tipsInfo;

        }


        /// <summary>
        ///  获取流，注意调用方要读取完毕需要关闭httpWebResponse、同时释放httpWebRequest
        /// </summary>
        /// <param name="httpurl"></param>
        /// <param name="httpWebRequest"></param>
        /// <param name="httpWebResponse"></param>
        /// <returns></returns>
        private Stream GetStream(string httpurl, out HttpWebRequest httpWebRequest, out HttpWebResponse httpWebResponse)
        {
            if (!httpurl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !httpurl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
            {
                throw new Exception("请求的地址必须以http://或https开头");
            }
            Stream stream = null;
            httpWebRequest = null;
            httpWebResponse = null;
            try
            {
                //https协议必须添加，否则请求会被中断或阻止
                if (httpurl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                {
                    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
                }
                httpWebRequest = (HttpWebRequest)WebRequest.Create(httpurl);
                httpWebRequest.AllowAutoRedirect = AllowAutoRedirect;
                if (!string.IsNullOrEmpty(Referer))
                {
                    httpWebRequest.Referer = Referer;
                }
                httpWebRequest.Proxy = null;
                httpWebRequest.CachePolicy = new RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                if (IsMobileAgent)
                {
                    httpWebRequest.UserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Mobile Safari/537.36";
                }
                else
                {
                    httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36";
                }
                if (HeaderDictionary != null)
                {
                    foreach (KeyValuePair<string, string> keyValue in HeaderDictionary)
                    {
                        httpWebRequest.Headers[keyValue.Key] = keyValue.Value;
                    }
                }
                //添加cookie
                SetCookie(httpWebRequest, httpurl);

                httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                //状态码参考：https://www.cnblogs.com/SavionZhang/p/11394494.html
                //不允许重定向
                if (!this.AllowAutoRedirect)
                {
                    string[] redirectCodes = new string[] { "Ambiguous", "Found", "MultipleChoices", "Redirect", "RedirectKeepVerb", "RedirectMethod", "SeeOther", "TemporaryRedirect" };
                    if (redirectCodes.Contains(httpWebResponse.StatusCode.ToString(), StringComparer.OrdinalIgnoreCase))
                    {
                        throw new HttpException("HTTP Error 404.0 - Not Found");
                    }
                }
                ResponseContentType = httpWebResponse.ContentType;
                stream = httpWebResponse.GetResponseStream();
                stream.ReadTimeout = TimeoutSecond * 1000;
                return stream;
            }
            catch (WebException ex)
            {
                string statusDescription = "";
                if (ex.Response != null)
                {
                    statusDescription = "," + ((HttpWebResponse)ex.Response).StatusDescription;
                }
                throw new Exception(ex.Message + statusDescription);
            }
            finally
            {
                //Close(httpWebRequest, httpWebResponse, stream);
            }
        }

        /// <summary>
        /// 设置cookie;
        /// </summary>
        /// <param name="httpWebRequest"></param>
        private void SetCookie(HttpWebRequest httpWebRequest,string httpurl)
        {
            if(this.CookieDictionary==null)
            {
                return;
            }
            CookieContainer cookieContainer = new CookieContainer();
            string domain = StringHelper.DomainFormat(httpurl,false).Split(':')[0];//不需要加端口
            foreach (KeyValuePair<string, string> item in this.CookieDictionary)
            {
                string value = item.Value;
                if (value != null)
                {
                    cookieContainer.Add(new Cookie(item.Key, value, "/", domain));
                }
            }
            httpWebRequest.CookieContainer = cookieContainer;
        }

        /// <summary>
        /// 关闭和和释放资源
        /// </summary>
        /// <param name="httpWebRequest"></param>
        /// <param name="httpWebResponse"></param>
        /// <param name="stream"></param>
        public void Close(HttpWebRequest httpWebRequest, HttpWebResponse httpWebResponse, Stream stream)
        {
            if (stream != null)
            {
                stream.Close();
            }
            if (httpWebResponse != null)
            {
                httpWebResponse.Close();
            }
            if (httpWebRequest != null)
            {
                httpWebRequest.Abort();
            }
        }
        /// <summary>
        /// 拼接Url参数
        /// </summary>
        /// <param name="parames"></param>
        /// <returns></returns>
        public string GetQueryString(Dictionary<string, string> parames)
        {
            if (parames == null)
            {
                return "";
            }
            StringBuilder query = new StringBuilder("");  //ulr字符串
            if (parames.Count == 0)
            {
                return "";
            }
            foreach (var item in parames)
            {
                string key = item.Key;
                string value = item.Value;
                if (string.IsNullOrEmpty(value))
                {
                    value = "";
                }
                if (!string.IsNullOrEmpty(key))
                {
                    query.Append("&").Append(UrlEncode(key)).Append("=").Append(UrlEncode(value));
                }
            }
            return query.ToString().Substring(1);
        }

        /// <summary>
        /// 对参数进行UrlEncode编码
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public String UrlEncode(string value)
        {
            return HttpUtility.UrlEncode(value, Encoding.UTF8).Replace("+", "%20").Replace("*", "%2A").Replace("%7E", "~");
        }


    }
}
